/**
*  \file i2c_hw.c
*
*  \brief i2c driver function
*
*  \author dajin.li  <dajin.li@linde-china.cn>
*
*/

/*===============================================[ private includes  ]================================================*/
#include <xmc_gpio.h>
#include <xmc_i2c.h>
#include <xmc4500/i2c_hw.h>
#include <xmc4500/io.h>
#include <xmc4500/processor.h>
#include <xmc4500/types_local.h>
#include "../sched/sched.h"
#include "../pal/pal_mem.h"

/*===============================================[ private defines   ]================================================*/
#define SDA_PIN               P0_5
#define SCL_PIN               P0_11
#define SDA_SOURCE            USIC1_C0_DX0_P0_5
#define SCL_SOURCE            USIC1_C0_DX1_P0_11

#define EE_I2C_CH             XMC_I2C1_CH0

#define i2c_delay_cnt         10 /*10*100us == 1ms*/
 
#define EEPROM_I2C_ADDR       0xA0
#define EEPROM_I2C_CHANNEL    XMC_I2C1_CH0
#define EEPROM_PAGE_LENGTH    0x40


enum i2c_hw_ch_tdf
{
    i2c_hw_ch_tdf_master_send         =  0U,
    i2c_hw_ch_tdf_slave_send          =  (u32)1U << 8U,
    i2c_hw_ch_tdf_master_receive_ack  =  (u32)2U << 8U,
    i2c_hw_ch_tdf_master_recevie_nack =  (u32)3U << 8U,
    i2c_hw_ch_tdf_master_start        =  (u32)4U << 8U,
    i2c_hw_ch_tdf_master_restart      =  (u32)5U << 8U,
    i2c_hw_ch_tdf_master_stop         =  (u32)6U << 8U
};



/*===============================================[ private variables ]================================================*/

XMC_GPIO_CONFIG_t i2c_sda =
{
  .mode = XMC_GPIO_MODE_OUTPUT_OPEN_DRAIN_ALT2,
  .output_strength = XMC_GPIO_OUTPUT_STRENGTH_STRONG_SHARP_EDGE
};

XMC_GPIO_CONFIG_t i2c_scl =
{
  .mode = XMC_GPIO_MODE_OUTPUT_OPEN_DRAIN_ALT2,
  .output_strength = XMC_GPIO_OUTPUT_STRENGTH_STRONG_SHARP_EDGE
};


XMC_I2C_CH_CONFIG_t i2c_cfg =
{
  .baudrate = 400000U,
};

static void i2c_master_transmit(XMC_USIC_CH_t *const channel, const u8 data);
static void i2c_master_start(XMC_USIC_CH_t *const channel, const uint16_t addr, const XMC_I2C_CH_CMD_t command);
static void i2c_master_repeated_start(XMC_USIC_CH_t *const channel, const u16 addr, const XMC_I2C_CH_CMD_t command);
static void i2c_master_stop(XMC_USIC_CH_t *const channel);
static void i2c_master_receive_ack(XMC_USIC_CH_t *const channel);
static void i2c_master_receive_nack(XMC_USIC_CH_t *const channel);



/*================================================[ public functions ]================================================*/

//--------------------------------------------------------------
/// \brief i2c_hardware_init
/// Initialize iic hardware
/// \return void
//--------------------------------------------------------------

void i2c_hardware_init(void)
{
    XMC_I2C_CH_Init(EEPROM_I2C_CHANNEL, &i2c_cfg);
    XMC_I2C_CH_SetInputSource(EEPROM_I2C_CHANNEL, XMC_I2C_CH_INPUT_SDA, SDA_SOURCE);
    XMC_I2C_CH_SetInputSource(EEPROM_I2C_CHANNEL, XMC_I2C_CH_INPUT_SCL, SCL_SOURCE);
    
    XMC_GPIO_Init(SCL_PIN, &i2c_scl);
    XMC_GPIO_Init(SDA_PIN, &i2c_sda);
    XMC_I2C_CH_Start(EEPROM_I2C_CHANNEL);
}
u8 i2c_master_read(u32 address, u8* data, u32 size)
{
    u8 pd_ret = 1;
    u32 current_tic = global_timer;
    if((data == 0) || (size == 0))
    {
        pd_ret = 0;
    }
    else
    {
        i2c_master_start(EEPROM_I2C_CHANNEL, EEPROM_I2C_ADDR, XMC_I2C_CH_CMD_WRITE);
        i2c_master_transmit(EEPROM_I2C_CHANNEL, (address >> 8) & 0xffU);
        i2c_master_transmit(EEPROM_I2C_CHANNEL, address & 0xffU);
        i2c_master_repeated_start(EEPROM_I2C_CHANNEL, EEPROM_I2C_ADDR, XMC_I2C_CH_CMD_READ);


        /* receive data, byte by byte */
        u32 data_index = 0;
        while (data_index < size)
        {
            XMC_I2C_CH_ClearStatusFlag(EEPROM_I2C_CHANNEL, XMC_I2C_CH_STATUS_FLAG_ALTERNATIVE_RECEIVE_INDICATION | XMC_I2C_CH_STATUS_FLAG_RECEIVE_INDICATION);

            if (data_index == (size - 1))
            {
                i2c_master_receive_nack(EEPROM_I2C_CHANNEL);
            }
            else
            {
                i2c_master_receive_ack(EEPROM_I2C_CHANNEL);
            }
            current_tic = global_timer;
            while ((XMC_I2C_CH_GetStatusFlag(EEPROM_I2C_CHANNEL) & (XMC_I2C_CH_STATUS_FLAG_ALTERNATIVE_RECEIVE_INDICATION | XMC_I2C_CH_STATUS_FLAG_RECEIVE_INDICATION)) == 0U)
            {
                if((global_timer - current_tic) > i2c_delay_cnt)
                {
                    break;
                }
            }

            data[data_index] = XMC_I2C_CH_GetReceivedData(EEPROM_I2C_CHANNEL);
            data_index++;
        }
        i2c_master_stop(EEPROM_I2C_CHANNEL);
    }
    return pd_ret;
}

u8 i2c_master_write(u32 address, u8* data, u32 size)
{
    u32 current_tic = global_timer;
    u32 buffer_size;
    u8 pd_ret = 1;
    if((data == 0) || (size == 0))
    {
        pd_ret =  0;
    }
    else
    {
        while (size > 0)
        {
            buffer_size = EEPROM_PAGE_LENGTH - (address & (EEPROM_PAGE_LENGTH - 1));
            if (buffer_size > size)
            {
                buffer_size = size;
            }
            
            i2c_master_start(EEPROM_I2C_CHANNEL, EEPROM_I2C_ADDR, XMC_I2C_CH_CMD_WRITE);
            i2c_master_transmit(EEPROM_I2C_CHANNEL, (address >> 8) & 0xffU);
            i2c_master_transmit(EEPROM_I2C_CHANNEL, address & 0xffU);

            /* send data, byte by byte */
            u32 buffer_index = 0;
            while (buffer_index < buffer_size)
            {
                i2c_master_transmit(EEPROM_I2C_CHANNEL, data[buffer_index]);
                buffer_index++;
            }
            i2c_master_stop(EEPROM_I2C_CHANNEL);

            /* polling on ACK to wait internal write is finished */
            while (true)
            {
                XMC_I2C_CH_ClearStatusFlag(EEPROM_I2C_CHANNEL, XMC_I2C_CH_STATUS_FLAG_NACK_RECEIVED | XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED);
                i2c_master_start(EEPROM_I2C_CHANNEL, EEPROM_I2C_ADDR, XMC_I2C_CH_CMD_WRITE);

                u32 status;
                current_tic = global_timer;
                do
                {
                    status = XMC_I2C_CH_GetStatusFlag(EEPROM_I2C_CHANNEL);
                    if((global_timer - current_tic) > i2c_delay_cnt)
                    {
                    break;
                    }
                }
                while ((status & (XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED | XMC_I2C_CH_STATUS_FLAG_NACK_RECEIVED)) == 0U);

                if ((status & (XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED | XMC_I2C_CH_STATUS_FLAG_NACK_RECEIVED)) == XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED)
                {
                    i2c_master_stop(EEPROM_I2C_CHANNEL);
                    break;
                }
                else
                {
                    XMC_I2C_CH_ClearStatusFlag(EEPROM_I2C_CHANNEL, 0xFFFFFFFFU);
                    XMC_USIC_CH_SetTransmitBufferStatus(EEPROM_I2C_CHANNEL, XMC_USIC_CH_TBUF_STATUS_SET_IDLE);
                }
            }

            address += buffer_size;
            data += buffer_size;
            size -= buffer_size;

        }
    }

    
    return pd_ret;
}

/* Sends master send command along with data to IN/TBUF register based on FIFO/non-FIFO modes. */
static void i2c_master_transmit(XMC_USIC_CH_t *const channel, const u8 data)
{
    u32 current_tic = global_timer;

    XMC_I2C_CH_ClearStatusFlag(EEPROM_I2C_CHANNEL, XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED);

    /* Check FIFO size */
    if ((channel->TBCTR & USIC_CH_TBCTR_SIZE_Msk) == 0U)
    {    
        while (XMC_USIC_CH_GetTransmitBufferStatus(channel) == XMC_USIC_CH_TBUF_STATUS_BUSY)
        {
            if((global_timer - current_tic) > i2c_delay_cnt)
            {
               break;
            }
        }

        /* clear PSR_TBIF */
        XMC_I2C_CH_ClearStatusFlag(channel, (u32)XMC_I2C_CH_STATUS_FLAG_TRANSMIT_BUFFER_INDICATION);

        channel->TBUF[0] = (u32)i2c_hw_ch_tdf_master_send | data;
    }
    else
    {
        channel->IN[0] = (u32)i2c_hw_ch_tdf_master_send | data;
    }
    
    /* wait for ACK */
    current_tic = global_timer;
    while ((XMC_I2C_CH_GetStatusFlag(EEPROM_I2C_CHANNEL) & XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED) == 0U)
    {
        if((global_timer - current_tic) > i2c_delay_cnt)
        {
            break;
        }
    }
    
}

/* Sends master start condition along with read/write command to IN/TBUF register based on FIFO/non-FIFO modes. */
static void i2c_master_start(XMC_USIC_CH_t *const channel, const uint16_t addr, const XMC_I2C_CH_CMD_t command)
{
    u32 temp;
    u32 current_tic = global_timer;
    
    XMC_I2C_CH_ClearStatusFlag(EEPROM_I2C_CHANNEL, XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED);

    temp = addr | (u32)i2c_hw_ch_tdf_master_start;
    if (command == XMC_I2C_CH_CMD_READ)
    {
        temp |= 0x1U;
    }

    /* Check FIFO size */
    if ((channel->TBCTR & USIC_CH_TBCTR_SIZE_Msk) == 0U)
    {
        while (XMC_USIC_CH_GetTransmitBufferStatus(channel) == XMC_USIC_CH_TBUF_STATUS_BUSY)
        {
            if((global_timer - current_tic) > i2c_delay_cnt)
            {
                break;
            }
        }

        /* clear PSR_TBIF */
        XMC_I2C_CH_ClearStatusFlag(channel, (u32)XMC_I2C_CH_STATUS_FLAG_TRANSMIT_BUFFER_INDICATION);

        channel->TBUF[0] = temp;
    }
    else
    {
        channel->IN[0U] = temp;
    }
    
    current_tic = global_timer;
    /* wait for ACK */
    while ((XMC_I2C_CH_GetStatusFlag(EEPROM_I2C_CHANNEL) & XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED) == 0U)
    {
        if((global_timer - current_tic) > 1)
        {
            break;
        }
    }
}
/* Sends master repeated start condition along with read/write command to IN/TBUF register based on FIFO/non-FIFO modes. */
static void i2c_master_repeated_start(XMC_USIC_CH_t *const channel, const u16 addr, const XMC_I2C_CH_CMD_t command)
{
    u32 tmp;
    u32 current_tic = global_timer;

    /* start reading */
    XMC_I2C_CH_ClearStatusFlag(EEPROM_I2C_CHANNEL, XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED);
    
    
    tmp = addr | (u32)i2c_hw_ch_tdf_master_restart;
    if (command == XMC_I2C_CH_CMD_READ)
    {
        tmp |= 0x1U;
    }

    /* Check FIFO size */
    if ((channel->TBCTR & USIC_CH_TBCTR_SIZE_Msk) == 0U)
    {  
        while (XMC_USIC_CH_GetTransmitBufferStatus(channel) == XMC_USIC_CH_TBUF_STATUS_BUSY)
        {
            if((global_timer - current_tic) > i2c_delay_cnt)
            {
                break;
            }
        }

        /* clear PSR_TBIF */
        XMC_I2C_CH_ClearStatusFlag(channel, (u32)XMC_I2C_CH_STATUS_FLAG_TRANSMIT_BUFFER_INDICATION);

        channel->TBUF[0] = tmp;
    }
    else
    {
        channel->IN[0U] = tmp;
    }
    /* wait for ACK */
    current_tic = global_timer;
    while ((XMC_I2C_CH_GetStatusFlag(EEPROM_I2C_CHANNEL) & XMC_I2C_CH_STATUS_FLAG_ACK_RECEIVED) == 0U)
    {
        if((global_timer - current_tic) > i2c_delay_cnt)
        {
            break;
        }
    }

    
}

/* Sends master stop command to IN/TBUF register based on FIFO/non-FIFO modes. */
static void i2c_master_stop(XMC_USIC_CH_t *const channel)
{
    u32 current_tic = global_timer;
    /* Check FIFO size */
    if ((channel->TBCTR & USIC_CH_TBCTR_SIZE_Msk) == 0U)
    {
        while (XMC_USIC_CH_GetTransmitBufferStatus(channel) == XMC_USIC_CH_TBUF_STATUS_BUSY)
        {
            if((global_timer - current_tic) > i2c_delay_cnt)
            {
                break;
            }
        }

        /* clear PSR_TBIF */
        XMC_I2C_CH_ClearStatusFlag(channel, (u32)XMC_I2C_CH_STATUS_FLAG_TRANSMIT_BUFFER_INDICATION);

        channel->TBUF[0] = (u32)i2c_hw_ch_tdf_master_stop;
    }
    else
    {
        channel->IN[0U] = (u32)i2c_hw_ch_tdf_master_stop;
    }
}
/* Sends master receive ack command to IN/TBUF register based on FIFO/non-FIFO modes. */
static void i2c_master_receive_ack(XMC_USIC_CH_t *const channel)
{
    u32 current_tic = global_timer;
    /* Check FIFO size */
    if ((channel->TBCTR & USIC_CH_TBCTR_SIZE_Msk) == 0U)
    {    
        while(XMC_USIC_CH_GetTransmitBufferStatus(channel) == XMC_USIC_CH_TBUF_STATUS_BUSY)
        {
            if((global_timer - current_tic) > i2c_delay_cnt)
            {
                break;
            }
        }

        /* clear PSR_TBIF */
        XMC_I2C_CH_ClearStatusFlag(channel, (uint32_t)XMC_I2C_CH_STATUS_FLAG_TRANSMIT_BUFFER_INDICATION);

        channel->TBUF[0] = (u32)i2c_hw_ch_tdf_master_receive_ack;
    }
    else
    {
        channel->IN[0] = (u32)i2c_hw_ch_tdf_master_receive_ack;
    }
}

/* Sends master receive nack command to IN/TBUF register based on FIFO/non-FIFO modes. */
static void i2c_master_receive_nack(XMC_USIC_CH_t *const channel)
{
    u32 current_tic = global_timer;
    /* Check FIFO size */
    if ((channel->TBCTR & USIC_CH_TBCTR_SIZE_Msk) == 0U)
    {    
        while(XMC_USIC_CH_GetTransmitBufferStatus(channel) == XMC_USIC_CH_TBUF_STATUS_BUSY)
        {
            if((global_timer - current_tic) > i2c_delay_cnt)
            {
                
                break;
            }
        }

        /* clear PSR_TBIF */
        XMC_I2C_CH_ClearStatusFlag(channel, (u32)XMC_I2C_CH_STATUS_FLAG_TRANSMIT_BUFFER_INDICATION);

        channel->TBUF[0] = (u32)i2c_hw_ch_tdf_master_recevie_nack;
    }
    else 
    {
        channel->IN[0] = (u32)i2c_hw_ch_tdf_master_recevie_nack;
    }
}


